package com.xqq.utils;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class SnowflakeIDGenerator {
    private static final long START_TIMESTAMP = 1632691200000L;
    private static final long SEQUENCE_BITS = 12;
    private static final long WORKER_ID_BITS = 10;
    private static final long MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;
    private static final long MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    private static long workerId;
    private static long sequence = 0;
    private static long lastTimestamp = -1;

    static {
        try {
            workerId = initializeWorkerId();
        } catch (UnknownHostException e) {
            throw new RuntimeException("无法获取本机IP地址", e);
        }
    }

    private static long initializeWorkerId() throws UnknownHostException {
        long workerId;
        byte[] ipAddress = InetAddress.getLocalHost().getAddress();
        workerId = ((ipAddress[ipAddress.length - 2] & 0xFF) << 8) | (ipAddress[ipAddress.length - 1] & 0xFF);
        return workerId & MAX_WORKER_ID;
    }

    public static synchronized long generateID() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨异常");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        lastTimestamp = timestamp;

        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) |
                (workerId << WORKER_ID_SHIFT) |
                sequence;
    }

    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
